using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.DirectSound;
using System.Threading;

namespace WAVMP3Recorder {
  class WAVRecorder {
    #region IXivoPlugin Members

    public void Init() {
    }
    #endregion

    CaptureBuffer RecordBuffer;
    public const int NumberRecordNotifications = 16;
    public BufferPositionNotify[] RecPositionNotify = new BufferPositionNotify[NumberRecordNotifications + 1];
    private Thread RecNotifyThread = null;
    public Notify RecNotify = null;
    public int NotifySize = 0;
    public int bufferSize = 0;
    public int NextCaptureOffset = 0;
    public bool Capturing = false;
    public AutoResetEvent RecNotificationEvent = null;
    Guid _DeviceGUID = Guid.Empty;

    System.IO.FileStream AudioFile;
    const int sampleRate = 44100;
    const int bitsPerSample = 16;
    const int channels = 2;
    const int bytesPerSample = bitsPerSample * channels / 8;
    const int bytesPerSecond = sampleRate * bytesPerSample;
    const int bufferSeconds = 3600; // 1 hour buffer
    const int MaxFileSize = bytesPerSecond * bufferSeconds;  // 1 minute buffer
    string _Filename = "";
    long headerSize;

    public string Filename {
      get {
        return _Filename;
      }
    }
    /// <summary>
    /// Filename needs to be the exact name of the file that will be recorded
    /// .wav will NOT be appended, so make sure you do it.
    /// </summary>
    /// <param name="Filename">Name of file to record. Include .wav extension.</param>
    public WAVRecorder(string Filename) {
      _DeviceGUID = Properties.Settings.Default.RecordDevice;
      _Filename = Filename + ".wav";
      StartRecording();
    }

    public void StopRecording() {
      Capturing = false;
      if (RecordBuffer != null) {
        RecordBuffer.Stop();
        RecNotificationEvent.Set();
      }
      RecNotifyThread = null;
      if (AudioFile != null) {
        writeHeader();
        AudioFile.Close();
        AudioFile = null;
      }
    }

    public void StartRecording() {
      if (Capturing == false) {
        AudioFile = new System.IO.FileStream(_Filename, System.IO.FileMode.OpenOrCreate);
        writeHeader();
        Capturing = true;
        CreateCaptureBuffer();
        RecordBuffer.Start(true);
      }
    }

    void CreateCaptureBuffer() {
      Console.WriteLine("Creating capture buffer");
      CaptureBufferDescription RecordBufferDesc = new CaptureBufferDescription();

      if (RecNotify != null) {
        RecNotify.Dispose();
        RecNotify = null;
      }
      if (RecordBuffer != null) {
        RecordBuffer.Dispose();
        RecordBuffer = null;
      }
      WaveFormat Format = new WaveFormat();
      Format.SamplesPerSecond = sampleRate;
      Format.BitsPerSample = bitsPerSample;
      Format.Channels = channels;
      Format.BlockAlign = (short)(Format.Channels * (Format.BitsPerSample / 8));
      Format.AverageBytesPerSecond = Format.BlockAlign * Format.SamplesPerSecond;

      // Set the notification size
      NotifySize = (1024 > Format.AverageBytesPerSecond / 8) ? 1024 : (Format.AverageBytesPerSecond / 8);
      NotifySize -= NotifySize % Format.BlockAlign;

      // Set the buffer sizes
      bufferSize = NotifySize * NumberRecordNotifications;

      // Create the capture buffer
      RecordBufferDesc.BufferBytes = bufferSize;
      Format.FormatTag = WaveFormatTag.Pcm;

      RecordBufferDesc.Format = Format; // Set the format during creatation

      Capture RecordDevice = new Capture(_DeviceGUID);
      try {
        RecordBuffer = new CaptureBuffer(RecordBufferDesc, RecordDevice);
      }
      catch (Exception ex) {
        Console.WriteLine(ex.Message);
        Console.WriteLine(ex.StackTrace);
      }
      NextCaptureOffset = 0;
      InitNotifications();

    }

    void InitNotifications() {
      //-----------------------------------------------------------------------------
      // Name: InitNotifications()
      // Desc: Inits the notifications on the capture buffer which are handled
      //       in the notify thread.
      //-----------------------------------------------------------------------------

      if (RecordBuffer == null)
        throw new NullReferenceException();

      // Create a thread to monitor the notify events
      if (RecNotifyThread == null) {
        RecNotifyThread = new Thread(new ThreadStart(RecWaitThread));
        Capturing = true;
        RecNotifyThread.Start();

        // Create a notification event, for when the sound stops playing
        RecNotificationEvent = new AutoResetEvent(false);
      }


      // Setup the notification positions
      for (int i = 0; i < NumberRecordNotifications; i++) {
        RecPositionNotify[i].Offset = (NotifySize * i) + NotifySize - 1;
        RecPositionNotify[i].EventNotifyHandle = RecNotificationEvent.Handle;

      }

      RecNotify = new Notify(RecordBuffer);

      // Tell DirectSound when to notify the app. The notification will come in the from 
      // of signaled events that are handled in the notify thread.
      RecNotify.SetNotificationPositions(RecPositionNotify, NumberRecordNotifications);
    }

    private void RecWaitThread() {
      while (Capturing) {
        //Sit here and wait for a message to arrive
        if (RecNotificationEvent == null) {
          System.Threading.Thread.Sleep(1000);
        }
        else {
          RecNotificationEvent.WaitOne(Timeout.Infinite, true);
          RecordCapturedData();
        }
      }
    }

    void RecordCapturedData() {
      //-----------------------------------------------------------------------------
      // Name: RecordCapturedData()
      // Desc: Copies data from the capture buffer to the disk buffer.
      //-----------------------------------------------------------------------------
      byte[] CaptureData = null;
      int ReadBufferPos;
      int CapturePos;
      int LockSize;

      RecordBuffer.GetCurrentPosition(out CapturePos, out ReadBufferPos);
      LockSize = ReadBufferPos - NextCaptureOffset;
      if (LockSize < 0)
        LockSize += bufferSize;

      // Block align lock size so that we are always write on a boundary
      LockSize -= (LockSize % NotifySize);

      if (0 == LockSize)
        return;

      // Read the capture buffer.
      CaptureData = (byte[])RecordBuffer.Read(NextCaptureOffset, typeof(byte), LockFlag.None, LockSize);
      if (AudioFile != null) {
        AudioFile.Write(CaptureData, 0, CaptureData.Length);
      }

      // Move the capture offset along
      NextCaptureOffset += CaptureData.Length;
      NextCaptureOffset %= bufferSize; // Circular buffer
    }

    static public void EnumCaptureDevices(System.Windows.Forms.ComboBox cboDevices) {
      Guid defaultGuid = Properties.Settings.Default.RecordDevice;
      CaptureDevicesCollection devices = new CaptureDevicesCollection();
      foreach (DeviceInformation info in devices) {
        ComboBoxItem<Guid> item = new ComboBoxItem<Guid>(info.Description, info.DriverGuid);
        cboDevices.Items.Add(item);
        if (info.DriverGuid == defaultGuid)
          cboDevices.SelectedItem = item;
      }
    }

    static public void setRecordDevice(Guid DeviceGUID) {
      Properties.Settings.Default.RecordDevice = DeviceGUID;
      Properties.Settings.Default.Save();
    }

    private void Close() {
    }

    public System.Guid DeviceGUID {
      set { _DeviceGUID = value; }
      get { return _DeviceGUID; }
    }

    /// <summary>
    /// Used to perform 2 different tasks: 
    /// When called the first time, it creates the raw header with no size information.
    /// When called on a file that has wav data in it, it sets the header to the correct size
    /// of the data. This is necessary for proper playback in WM player and for conversion
    /// by LAME.
    /// </summary>
    private void writeHeader() {
      byte[] data = new byte[] {
        // RIFF chunk
        0x52,0x49,0x46,0x46, //RIFF
        0xff,0xff,0xff,0xff, //file size-8
        0x57,0x41,0x56,0x45, //WAVE

        // Format chunk
        0x66,0x6d,0x74,0x20, // fmt
        0x12,0x00,0x00,0x00,   // chunk data size (16+extra format bytes)
        0x01,0x00,           // compression code (1)
        0x02,0x00,           // number of channels
        0x44,0xac,0x00,0x00, // sample rate (44100, 0xAC44)
        0x10,0xb1,0x02,0x00, // average sample Bps (176400, 0x2B110)
        0x04,0x00,           // block align
        0x10,0x00,           // bits per sample
        0x00,0x00,           // extra data

        // wav chunk
        0x64,0x61,0x74,0x61, // data
        0xff,0xff,0xff,0xff  // chunk size
      };
      headerSize = data.Length;

      long fileSize = AudioFile.Length;
      System.Diagnostics.Debug.WriteLine("file size: " + fileSize.ToString());
      long tmp = fileSize - 8;
      for (int i = 4; i <= 7; ++i) {
        data[i] = (byte)tmp;
        tmp /= 0x100;
      }

      tmp = fileSize - 46;
      System.Diagnostics.Debug.WriteLine("data size: " + tmp.ToString());
      System.Diagnostics.Debug.WriteLine("data size: " + tmp.ToString("X"));
      for (int i = 42; i <= 45; ++i) {
        data[i] = (byte)tmp;
        System.Diagnostics.Debug.Write(data[i].ToString("X") + " ");
        tmp /= 0x100;
      }
      System.Diagnostics.Debug.WriteLine("");

      AudioFile.Position = 0;
      //AudioFile.Seek(0, System.IO.SeekOrigin.Begin);
      AudioFile.Write(data, 0, data.Length);
    }

  }
}
